/*
 * Cute4j.java  0.4.0 / Oct 23, 2010
 *
 * Copyright (c) 2010 - 2011, cute4j.com and contributors.
 *
 * This software is licensed under the LGPL version 2.1 license.
 * A copy of this license is included in the file LICENSE-LGPL.txt.
 */
package com.cute4j;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import com.cute4j.logging.Logger;

/**
 * Qt libraries for Java developers.
 *
 * @cute4j.native
 *    generate="header"
 *
 * @author Slobodan Vrkacevic
 * @version 0.4.0
 */
public final class Cute4j {

// Public Static Fields. -------------------------------------------------------

  /**
   * Debug configuration switch (the value is {@value}).
   */
  public static final boolean DEBUG = false;

  /**
   * Verbose configuration switch (the value is {@value}).
   */
  public static final boolean VERBOSE = false;

  /**
   * The library version (the value is {@value}).
   */
  public static final int VERSION = 0x000400;

  /**
   * The library version string (the value is {@value}).
   */
  public static final String VERSION_STRING = "0.4.0";

  /**
   * The library date string (the value is {@value}).
   */
  public static final String DATE_STRING = "2011-03-13";

// Package-Private Static Fields. ----------------------------------------------

  /**
   * The library version (the value is {@value}).
   */
  static final int NATIVE_VERSION = 0x000400;

  /**
   * The library version string (the value is {@value}).
   */
  static final String NATIVE_VERSION_STRING = "0.4.0";

// Private Static Configuration Fields. ----------------------------------------

  private static final int NATIVE_CONFIG_STATIC = 1;
  private static final int NATIVE_CONFIG_WIN32_STATIC_MCVCR = 2;
  private static final int NATIVE_CONFIG_WIN32_STATIC_MINGW = 3;
  private static final int NATIVE_CONFIG_DYNAMIC = 4;
  private static final int NATIVE_CONFIG_WIN32_DYNAMIC_MCVCR = 5;
  private static final int NATIVE_CONFIG_WIN32_DYNAMIC_MINGW = 6;
  
// Private Static Fields. ------------------------------------------------------

  private static boolean initialized;
  private static final String NATIVE_LIBRARY_NAME = "Cute4j";
  private static final int NATIVE_CONFIG = NATIVE_CONFIG_STATIC;

// Private Constructor. --------------------------------------------------------

 /**
  * Class has no instances.
  */
  private Cute4j() {
    // empty constructor body
  }

// Public Static Methods. ------------------------------------------------------

  /**
   * Returns <tt>true</tt> if the native library is successfully initialized.
   *
   * @return <tt>true</tt> if the native library is successfully initialized
   */
  public static boolean isInitialized() {
    return initialized;
  }

  /**
   * Initializes the library.
   */
  public static void initialize() {
    if (initialized) {
      return;
    }
    initialized = true;
    try {
      loadNativeLibraries();
      if (VERBOSE) {
        Logger.logInfo("Native libraries loaded");
      }
      _initialize(Logger.class);
      System.setProperty("cute4j.version", VERSION_STRING);
    } catch (Throwable t) {
      Logger.logError("Could not load native libraries", t);
    }
  }

// Private Static Methods. -----------------------------------------------------

  /**
   * Loads all native libraries. Also, extracts natives if necessary.
   */
  private static void loadNativeLibraries() throws UnsatisfiedLinkError {
    // TODO: implement better - there are some bugs
    if (loadDefaultLibraries()) {  // first load is from the default location
      return;                      // (java.library.path)
    }
    String tempDir = System.getProperty("java.io.tmpdir");
    if (tempDir == null) {
      throw new java.lang.Error("No tmpdir");
    }
    if (!tempDir.endsWith(File.separator)) {
      tempDir = tempDir + File.separator;
    }
    final String nativeFolder = tempDir + NATIVE_LIBRARY_NAME + "_" +
                                NATIVE_VERSION_STRING + File.separator;
    if (!extractNativeLibraries(nativeFolder)) {
      throw new UnsatisfiedLinkError("Unable to extract native libraries");
    }
    if (loadExtractedLibraries(nativeFolder)) {  // second load is from
      return;                                    // "java.io.tmpdir" location
    }
    throw new UnsatisfiedLinkError("Unable to load native libraries");
  }

  private static boolean loadDefaultLibraries() {
    switch (NATIVE_CONFIG) {
      case NATIVE_CONFIG_STATIC:
        return loadNativeStatic();
      case NATIVE_CONFIG_WIN32_STATIC_MCVCR:
        return loadWin32StaticMsvcr();
      case NATIVE_CONFIG_WIN32_STATIC_MINGW:
        return loadWin32StaticMingw();
      case NATIVE_CONFIG_DYNAMIC:
        return loadNativeDynamic();
      case NATIVE_CONFIG_WIN32_DYNAMIC_MCVCR:
        return loadWin32DynamicMsvcr();
      case NATIVE_CONFIG_WIN32_DYNAMIC_MINGW:
        return loadWin32DynamicMingw();
      default:
        throw new InternalError("No native configuration");
    }
  }

  private static boolean loadExtractedLibraries(String nativeFolder) {
    switch (NATIVE_CONFIG) {
      case NATIVE_CONFIG_STATIC:
        return loadNativeStatic(nativeFolder);
      case NATIVE_CONFIG_WIN32_STATIC_MCVCR:
        return loadWin32StaticMsvcr(nativeFolder);
      case NATIVE_CONFIG_WIN32_STATIC_MINGW:
        return loadWin32StaticMingw(nativeFolder);
      case NATIVE_CONFIG_DYNAMIC:
        return loadNativeDynamic(nativeFolder);
      case NATIVE_CONFIG_WIN32_DYNAMIC_MCVCR:
        return loadWin32DynamicMsvcr(nativeFolder);
      case NATIVE_CONFIG_WIN32_DYNAMIC_MINGW:
        return loadWin32DynamicMingw(nativeFolder);
      default:
        throw new InternalError("No native configuration");
    }
  }

  private static boolean extractNativeLibraries(String nativeFolder) {
    File tempLibFolder = new File(nativeFolder);
    if (!tempLibFolder.exists()) {
      tempLibFolder.mkdirs();
    }
    switch (NATIVE_CONFIG) {
      case NATIVE_CONFIG_STATIC:
        return extractNativeStatic(nativeFolder);
      case NATIVE_CONFIG_WIN32_STATIC_MCVCR:
        return extractWin32StaticMsvcr(nativeFolder);
      case NATIVE_CONFIG_WIN32_STATIC_MINGW:
        return extractWin32StaticMingw(nativeFolder);
      case NATIVE_CONFIG_DYNAMIC:
        return extractNativeDynamic(nativeFolder);
      case NATIVE_CONFIG_WIN32_DYNAMIC_MCVCR:
        return extractWin32DynamicMsvcr(nativeFolder);
      case NATIVE_CONFIG_WIN32_DYNAMIC_MINGW:
        return extractWin32DynamicMingw(nativeFolder);
      default:
        throw new InternalError("No native configuration");
    }
  }

  private static boolean loadNativeStatic() {
    return loadLibrary(NATIVE_LIBRARY_NAME);
  }

  private static boolean loadWin32StaticMsvcr() {
    if (!loadLibrary("msvcr80")) {
      return false;
    }
    return loadNativeStatic();
  }

  private static boolean loadWin32StaticMingw() {
    if (!loadLibrary("mingwm10")) {
      return false;
    }
    return loadNativeStatic();
  }

  private static boolean loadDependancies() { // TODO: use or remove
    // TODO: implement something like this
    if (!loadLibrary("msvcr80")) {
      return false;
    }
    return loadLibrary("mingwm10");
  }

  private static boolean loadQtDynamic() { // TODO: use or remove
    if (!loadLibrary("QtCore4")) {
      return false;
    }
    if (!loadLibrary("QtGui4")) {
      return false;
    }
    return loadLibrary("QtOpenGL4");
  }

  private static boolean loadQtLibraries() { // TODO: use or remove
    if (!loadLibrary("QtCore4")) {
      return false;
    }
    if (!loadLibrary("QtGui4")) {
      return false;
    }
    return loadLibrary("QtOpenGL4");
  }

  private static boolean loadNativeDynamic() {
    if (!loadLibrary("QtCore4")) {
      return false;
    }
    if (!loadLibrary("QtGui4")) {
      return false;
    }
    if (!loadLibrary("QtOpenGL4")) {
      return false;
    }
    return loadLibrary(NATIVE_LIBRARY_NAME);
  }

  private static boolean loadWin32DynamicMsvcr() {
    if (!loadLibrary("msvcr80")) {
      return false;
    }
    return loadNativeDynamic();
  }

  private static boolean loadWin32DynamicMingw() {
    if (!loadLibrary("mingwm10")) {
      return false;
    }
    return loadNativeDynamic();
  }

  private static boolean loadNativeStatic(String nativeFolder) {
    return loadMappedLibrary(nativeFolder +
                             System.mapLibraryName(NATIVE_LIBRARY_NAME));
  }

  private static boolean loadWin32StaticMsvcr(String nativeFolder) {
    if (!loadMappedLibrary(nativeFolder + "msvcr80.dll")) {
      return false;
    }
    return loadNativeStatic(nativeFolder);
  }

  private static boolean loadWin32StaticMingw(String nativeFolder) {
    if (!loadMappedLibrary(nativeFolder + "mingwm10.dll")) {
      return false;
    }
    return loadNativeStatic(nativeFolder);
  }

  private static boolean loadNativeDynamic(String nativeFolder) {
    if (!loadMappedLibrary(nativeFolder + System.mapLibraryName("QtCore4"))) {
      return false;
    }
    if (!loadMappedLibrary(nativeFolder + System.mapLibraryName("QtGui4"))) {
      return false;
    }
    if (!loadMappedLibrary(nativeFolder + System.mapLibraryName("QtOpenGL4"))) {
      return false;
    }
    return loadMappedLibrary(nativeFolder +
                             System.mapLibraryName(NATIVE_LIBRARY_NAME));
  }

  private static boolean loadWin32DynamicMsvcr(String nativeFolder) {
    if (!loadMappedLibrary(nativeFolder + "msvcr80.dll")) {
      return false;
    }
    return loadNativeDynamic(nativeFolder);
  }

  private static boolean loadWin32DynamicMingw(String nativeFolder) {
    if (!loadMappedLibrary(nativeFolder + "mingwm10.dll")) {
      return false;
    }
    return loadNativeDynamic(nativeFolder);
  }

  private static boolean extractNativeStatic(String nativeFolder) {
    final String nativeLibraryName = System.mapLibraryName(NATIVE_LIBRARY_NAME);
    return extractNativeResource(nativeFolder + nativeLibraryName,
                                 "native/" + nativeLibraryName);
  }

  private static boolean extractWin32StaticMsvcr(String nativeFolder) {
    if (!extractNativeResource(nativeFolder + "msvcr80.dll",
                               "native/msvcr80.dll")) {
      return false;
    }
    return extractNativeStatic(nativeFolder);
  }
  
  private static boolean extractWin32StaticMingw(String nativeFolder) {
    if (!extractNativeResource(nativeFolder + "mingwm10.dll",
                               "native/mingwm10.dll")) {
      return false;
    }
    return extractNativeStatic(nativeFolder);
  }

  private static boolean extractNativeDynamic(String nativeFolder) {
    final String nativeQtCoreName = System.mapLibraryName("QtCore4");
    if (!extractNativeResource(nativeFolder + nativeQtCoreName,
                               "native/" + nativeQtCoreName)) {
      return false;
    }
    final String nativeQtGuiName = System.mapLibraryName("QtGui4");
    if (!extractNativeResource(nativeFolder + nativeQtGuiName,
                               "native/" + nativeQtGuiName)) {
      return false;
    }
    final String nativeQtOpenGLName = System.mapLibraryName("QtOpenGL4");
    if (!extractNativeResource(nativeFolder + nativeQtOpenGLName,
                               "native/" + nativeQtOpenGLName)) {
      return false;
    }
    return extractNativeStatic(nativeFolder);
  }

  private static boolean extractWin32DynamicMsvcr(String nativeFolder) {
    if (!extractNativeResource(nativeFolder + "msvcr80.dll",
                               "native/msvcr80.dll")) {
      return false;
    }
    return extractNativeDynamic(nativeFolder);
  }

  private static boolean extractWin32DynamicMingw(String nativeFolder) {
    if (!extractNativeResource(nativeFolder + "mingwm10.dll",
                               "native/mingwm10.dll")) {
      return false;
    }
    return extractNativeDynamic(nativeFolder);
  }

  private static boolean loadLibrary(String libraryName) {
    try {
      System.loadLibrary(libraryName);
      if (VERBOSE) {
        Logger.logInfo("Native library loaded: " +
                       System.mapLibraryName(libraryName));
      }
      return true;
    } catch (UnsatisfiedLinkError ule) {
      if (VERBOSE) {
        Logger.logWarning("Could not load native library: " + libraryName, ule);
      }
      return false;
    }
  }

  private static boolean loadMappedLibrary(String libraryName) {
    try {
      System.load(libraryName);
      if (VERBOSE) {
        Logger.logInfo("Native library loaded: " + libraryName);
      }
      return true;
    } catch (UnsatisfiedLinkError ule) {
      if (VERBOSE) {
        Logger.logWarning("Could not load native library: " + libraryName, ule);
      }
      return false;
    }
  }

  private static boolean extractNativeResource(String nativeFileName,
                                               String resrouceName) {
    File nativeFile = new File(nativeFileName);
    if (nativeFile.exists()) {
      return true;
    }
    InputStream resourceStream = 
        ClassLoader.getSystemResourceAsStream(resrouceName);
    if (resourceStream != null) {
      FileOutputStream fos = null;
      try {
        fos = new FileOutputStream(nativeFile);      
        final int bufferSize = 16 * 1024;
        byte[] readBuffer = new byte[bufferSize];
        int bytesRead = 0;
        while (bytesRead != -1) {
          bytesRead = resourceStream.read(readBuffer, 0, bufferSize);
          if (bytesRead != -1) {
            fos.write(readBuffer, 0, bytesRead);
          }
        }
        if (VERBOSE) {
          Logger.logInfo("Native resource extracted: " +  nativeFileName);
        }
        return true;
      } catch (IOException ioe) {
        if (VERBOSE) {
          Logger.logWarning("Unable to extract native resource: " +
                            resrouceName, ioe);
        }
      } finally {
        if (fos != null) {
          try {
            fos.close();
          } catch (IOException ioe) {
            // empty catch block
          }
        }
        try {
          resourceStream.close();
        } catch (IOException ioe) {
          // empty catch block
        }
      }
    }
    return false;
  }

// Private Static Native Methods. ----------------------------------------------

  private static native void _initialize(Class loggerClass);

}
